package shorturl

import (
	"crypto/md5"
	"fmt"
	"io"
	mrand "math/rand"
	"strconv"
	"strings"
	"sync/atomic"
)

const (
	DEFALUT_SECURITY_KEY  = "ShortUrlSecurityKey"
	DEFALUT_SHORTURL_SIZE = 6
)

var (
	chars     []string = getAlphanumeric()
	globalnum int64    = 100000000
)

//MD5分组(http://www.oschina.net/code/snippet_54124_7837)
//1.将原始长链接(long)进行MD5加密，为了避免防止算法泄漏，可以在原链接上添加自定义的字符串作为密钥(key)
//2.把128位的MD分成四组(shortUrl [] string)，每组32位，对应一个候选短链接
//3.对于每个32位的数，将它与0x3FFFFFFF(c1)进行位与运算，取其低30位的数据(gv)
//4.把得到的值(gv)与0x0000003D(c2)进行位与运算,得到与运算数据(index)
//  再把得到的结果(index)作为下标在字符表中选取字符
//  再把原数字(gv)右移5位进行相同操作
//  重复4步骤进行size次得到size个字符，即组成一个候选短链接地址
//5.在4个候选短链接中随机选择一个作为最终的短链接
func BaseMD5GenerateShortUrl(long, key string, size int) (short string) {
	hashValue := md5Hash(long, key)

	var c1, _ = strconv.ParseInt("3FFFFFFF", 16, 64)
	var c2, _ = strconv.ParseInt("0000003D", 16, 64)

	var shortUrl [4]string

	for i, l := 0, len(shortUrl); i < l; i++ {
		group := substring(hashValue, i*8, 8)
		iv, _ := strconv.ParseInt(group, 16, 64)
		gv := c1 & iv
		var rs string
		for j := 0; j < size; j++ {
			index := c2 & gv
			rs += chars[index]
			gv = gv >> 5
		}
		shortUrl[i] = rs
	}
	return shortUrl[mrand.Intn(len(shortUrl))]
}

//使用62进制[0-9a-zA-Z]
func Base62GenerateShortUrlBase62() string {
	atomic.AddInt64(&globalnum, 1)
	num := globalnum
	if num < 62 {
		return chars[num]
	} else {
		var runes []string
		runes = append(runes, chars[num%62])
		num = num / 62
		for num >= 1 {
			if num < 62 {
				runes = append(runes, chars[num-1])
			} else {
				runes = append(runes, chars[num%62])
			}
			num = num / 62
		}
		for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
			runes[i], runes[j] = runes[j], runes[i]
		}
		return strings.Join(runes, "")
	}
}

func GetUrlMd5(long string) string {
	return md5Hash(long, DEFALUT_SECURITY_KEY)
}

//Get alpha and numberic to string array
//Alpha [A-Z] => [65-90] [a-z] = >[97-122]
//Numberic [0-9] [48-57]
func getAlphanumeric() []string {
	alphanumeric := mergeRange(getRange(48, 57), getRange(65, 90))
	alphanumeric = mergeRange(alphanumeric, getRange(97, 122))
	return alphanumeric
}

func getRange(start, end int) (ran []string) {
	for i := start; i <= end; i++ {
		ran = append(ran, string(byte(i)))
	}
	return ran
}

func mergeRange(a, b []string) []string {
	c := make([]string, len(a)+len(b))
	copy(c, a)
	copy(c[len(a):], b)
	return c
}

func substring(str string, start, length int) string {
	rs := []rune(str)
	rslen := len(rs)
	end := 0
	if start < 0 {
		start = rslen - 1 + start
	}
	end = start + length
	if start > end {
		start, end = end, start
	}
	if start < 0 {
		start = 0
	}
	if start > rslen {
		start = rslen
	}
	if end < 0 {
		end = 0
	}
	if end > rslen {
		end = rslen
	}
	return string(rs[start:end])
}

func md5Hash(long, key string) string {
	vmd5 := md5.New()
	io.WriteString(vmd5, long+key)
	hashValue := fmt.Sprintf("%x", vmd5.Sum(nil))
	return hashValue
}
